/**
 * 用户数据统计脚本
 *
 * Created by raquelmao on 2020/09/24.
 */
const fs = require('fs-extra');
const path = require('path');
const program = require('commander');
const mongoose = require('mongoose');
const mongoClient = require('./../../phoenix/models/mongo/client');
const ProjectVersions = require('./../../phoenix/models/mongo/project-version');
const Projects = require('./../../phoenix/models/mongo/project');
const TestCases = require('./../../phoenix/models/mongo/test-case');
const Users = require('./../../phoenix/models/mongo/user');
const Tasks = require('./../../phoenix/models/mongo/task');
const files = require('./../../phoenix/models/files');
const utility = require('./../../phoenix/models/utility');
const projectVersions = require('./../../phoenix/models/project-versions');
const functions = require('./../../phoenix/models/functions');

const taskType = {
  1: 'parse',
  2: 'generate test cases',
  3: 'run test cases',
  4: 'auto test',
};

/**
 * 获取版本测试用例数目
 * @param {String} versionId 版本id
 * @param {Object} options 选项
 * @return {Promise}
 */
function getCount(versionId, options = {}) {
  options.versionId = mongoose.Types.ObjectId(versionId);
  return TestCases.countDocuments(options);
}

/**
 * 获取单个版本信息
 * @param {Object} version 版本信息
 * @return {Promise}
 */
function getVersionStatistics(version) {
  const projectVersionId = version._id; // eslint-disable-line no-underscore-dangle
  const statistics = {
    versionName: version.versionName,
    coverages: {
      statementCoverage: 0,
      branchCoverage: 0,
      conditionCoverage: 0,
      mcdcCoverage: 0,
    },
    function: {
      total: 0,
    },
    testCases: {
      total: 0,
    },
    file: {
      total: 0,
      headers: {
        total: 0,
        compileFailedCount: 0,
        compiledCount: 0,
      },
      languages: {
        c: {
          total: 0,
        },
        'c++': {
          total: 0,
        },
        compileFailedCount: 0,
        compiledCount: 0,
      },
    },
  };
  return new Promise((resolve, reject) => {
    Promise.resolve()
      .then(() => {
        if (version.rootDirectoryId) {
          return projectVersions.complementRootDirectory([version]);
        }
        return Promise.resolve();
      })
      .then(() => files.getGroupCount(projectVersionId, 'extname')) // 根据文件后缀名分类统计源文件头文件个数
      .then((data) => {
        let extensions = [];
        data.forEach(({ _id, count }) => {
          const headersSet = new Set(version.headers.suffixes);
          let cSuffixes = [];
          let cPlusSuffixes = [];
          Object.keys(version.languages).forEach((key) => {
            if (key === 'c') {
              cSuffixes = cSuffixes.concat(version.languages[key].suffixes);
            } else if (key === 'c++') {
              cPlusSuffixes = cPlusSuffixes.concat(version.languages[key].suffixes);
            }
          });
          extensions = extensions.concat(cSuffixes);
          extensions = extensions.concat(cPlusSuffixes);
          const cSet = new Set(cSuffixes);
          const cPlusSet = new Set(cPlusSuffixes);
          if (headersSet.has(_id)) {
            statistics.file.total += count;
            statistics.file.headers.total += count;
          } else if (cSet.has(_id)) {
            statistics.file.total += count;
            statistics.file.languages.c.total += count;
          } else if (cPlusSet.has(_id)) {
            statistics.file.total += count;
            statistics.file.languages['c++'].total += count;
          }
        });
        return files.getGroupCount(projectVersionId, 'status', { extensions }); // 根据 源文件下 文件编译状态分类统计
      })
      .then((data) => {
        data.forEach(({ _id, count }) => {
          if (_id === files.statusKind.compiled) {
            statistics.file.languages.compiledCount += count;
          } else if (_id === files.statusKind.compileFailed) {
            statistics.file.languages.compileFailedCount += count;
          }
        });
        const extensions = version.headers.suffixes;
        return files.getGroupCount(projectVersionId, 'status', { extensions }); // 根据 头文件 文件编译状态分类统计
      })
      .then((data) => {
        data.forEach(({ _id, count }) => {
          if (_id === files.statusKind.compiled) {
            statistics.file.headers.compiledCount += count;
          } else if (_id === files.statusKind.compileFailed) {
            statistics.file.headers.compileFailedCount += count;
          }
        });
        return functions.getCount(projectVersionId);
      })
      .then((count) => {
        statistics.function.total = count;
        return getCount(projectVersionId);
      })
      .then((count) => {
        statistics.testCases.total = count;
        if (version.rootDirectory) {
          let suffixes = [];
          Object.keys(version.languages).forEach((key) => {
            suffixes = suffixes.concat(version.languages[key].suffixes);
          });
          const extensions = suffixes.concat(version.headers.suffixes);
          const coverages = files.getSummaryJson(version.rootDirectory, extensions);
          statistics.coverages.statementCoverage = coverages.statementCoverage;
          statistics.coverages.branchCoverage = coverages.branchCoverage;
          statistics.coverages.conditionCoverage = coverages.conditionCoverage;
          statistics.coverages.mcdcCoverage = coverages.mcdcCoverage;
        }
        const data = {};
        data[projectVersionId] = statistics;
        resolve(data);
      })
      .catch(reject);
  });
}

/**
 * 获取任务详情
 * @param {ObjectId} creatorId 用户Id
 * @param {number} spent 耗时
 * @param {string} messgae max/min
 * @return {Promise}
 */
function getTaskDetails(creatorId, spent, messgae) {
  const taskInfo = {};
  taskInfo[creatorId] = {};
  taskInfo[creatorId][messgae] = {};
  return new Promise((resolve, reject) => {
    Tasks.findOne({ creatorId, spent })
      .then((res) => {
        taskInfo[creatorId][messgae].version = `${res.projectName}.${res.versionName}`;
        taskInfo[creatorId][messgae].type = taskType[res.type];
        taskInfo[creatorId][messgae].spent = spent;
        resolve(taskInfo);
      })
      .catch(reject);
  });
}

/**
 * 获取用户任务统计信息合集
 * @return {Promise}
 */
function getTasksStatistics() {
  return new Promise((resolve, reject) => {
    const tasksStatistics = {};
    Tasks.aggregate([{
      $group: {
        _id: '$creatorId',
        maxTimeTask: { $max: '$spent' },
        miniTimeTask: { $min: '$spent' },
      },
    }])
      .then((res) => {
        const promises = [];
        res.forEach(({ _id, maxTimeTask, miniTimeTask }) => {
          promises.push(getTaskDetails(_id, maxTimeTask, 'maxTimeTask'));
          promises.push(getTaskDetails(_id, miniTimeTask, 'minTimeTask'));
        });
        return Promise.all(promises);
      })
      .then((res) => {
        res.forEach((taskObj) => {
          const taskCreatorId = Object.keys(taskObj)[0];
          if (!tasksStatistics[taskCreatorId]) {
            tasksStatistics[taskCreatorId] = {};
          }
          Object.assign(tasksStatistics[taskCreatorId], taskObj[taskCreatorId]);
        });
        resolve(tasksStatistics);
      })
      .catch(reject);
  });
}

/**
 * 按照project分类versions
 * @return {Promise}
 */
function getVersionsStatistics() {
  return new Promise((resolve, reject) => {
    ProjectVersions.aggregate([{
      $group: {
        _id: '$projectId',
        versions: { $push: '$_id' },
      },
    }])
      .then((res) => {
        const versions = {};
        res.forEach((version) => {
          versions[version._id.toString()] = version; // eslint-disable-line no-underscore-dangle
          delete version._id; // eslint-disable-line no-underscore-dangle
        });
        resolve(versions);
      })
      .catch(reject);
  });
}

/**
 * 获取用户统计数据
 * @param {Object} user 用户数据
 * @return {Promise<unknown>}
 */
function getUserProjectsStatistics(user) {
  return new Promise((resolve, reject) => {
    const statistics = {
      creatorId: user._id, // eslint-disable-line no-underscore-dangle
      visitCount: user.visitCount,
      lastVisitAt: user.lastVisitAt,
      projects: {},
    };
    const creatorId = mongoose.Types.ObjectId(user._id); // eslint-disable-line no-underscore-dangle
    Projects.find({ creatorId })
      .then((projects) => {
        projects.forEach((project) => {
          statistics.projects[project.projectName] = {
            projectId: project._id.toString(), // eslint-disable-line no-underscore-dangle
          };
        });
        const userInfo = {};
        userInfo[user.username] = statistics;
        resolve(userInfo);
      })
      .catch(reject);
  });
}

/**
 * 处理数据
 * @param {Object} users 用户数据
 * @param {Object} tasks 人物数据
 * @param {Object} versions 版本数据
 * @return {Object} 处理完的数据
 */
function handleData(users, tasks, versions, projectVersions) {
  Object.keys(users).forEach((userName) => {
    const user = users[userName];
    const { creatorId } = user;
    Object.assign(user, tasks[creatorId]);
    delete user.creatorId;
    Object.keys(user.projects).forEach((projectName) => {
      const { projectId } = user.projects[projectName];
      Object.assign(user.projects[projectName], projectVersions[projectId]);
      if (user.projects[projectName].versions) {
        for (let i = 0; i < user.projects[projectName].versions.length; i += 1) {
          const versionId = user.projects[projectName].versions.shift();
          user.projects[projectName].versions.push(versions[versionId]);
        }
      }
      delete user.projects[projectName].projectId;
    });
  });
  return users;
}

/**
 * 获取统计数据
 * @return {Promise}
 */
function getData() {
  return new Promise((resolve, reject) => {
    const versionsData = {}; // 版本数据合集
    const usersData = {}; // 用户数据合集，不包括任务数据
    let tasksData; // 任务统计数据合集
    let projectVersions; // 版本数据合集,key为projectId
    ProjectVersions.find()
      .then((versions) => {
        // eslint-disable-next-line arrow-body-style
        const promises = versions.map((version) => {
          return getVersionStatistics(version); // 获取每个版本数据
        });
        return Promise.all(promises);
      })
      .then((res) => {
        res.forEach((version) => {
          const versionName = Object.keys(version)[0];
          versionsData[versionName] = version[versionName];
        });
        return Users.find();
      })
      .then((users) => {
        // eslint-disable-next-line arrow-body-style
        const promises = users.map((user) => {
          return getUserProjectsStatistics(user);
        });
        return Promise.all(promises);
      })
      .then((res) => {
        res.forEach((userObj) => {
          const username = Object.keys(userObj)[0];
          usersData[username] = userObj[username];
        });
        return getTasksStatistics();
      })
      .then((res) => {
        tasksData = res;
        return getVersionsStatistics();
      })
      .then((res) => {
        projectVersions = res;
        resolve(handleData(usersData, tasksData, versionsData, projectVersions));
      })
      .catch(reject);
  });
}

function getCSVContent(data) {
  const templatePath = path.join(__dirname, 'statistics.csv.dot');
  Object.keys(data).forEach((userName) => {
    if (!data[userName].maxTimeTask) {
      data[userName].maxTimeTask = {};
    }
    if (!data[userName].minTimeTask) {
      data[userName].minTimeTask = {};
    }
    Object.keys(data[userName].projects).forEach((projectName) => {
      data[userName].projects[projectName].versions.forEach((version) => {
        ['statementCoverage', 'branchCoverage', 'conditionCoverage', 'mcdcCoverage'].forEach((coverageName) => {
          if (!version.coverages[coverageName] && version.coverages[coverageName] !== 0) {
            version.coverages[coverageName] = '';
          } else {
            version.coverages[coverageName] = `${version.coverages[coverageName] * 100}%`;
          }
        });
      });
    });
  });
  return utility.getDotTemplate(templatePath, data);
}

function getStatistics(format, outputPath) {
  fs.readJson(path.join(__dirname, '..', '..', 'phoenix', 'config.json'))
    .then((config) => {
      global.config = config;
      return mongoClient.connect();
    })
    .then(() => getData())
    .then((data) => {
      switch (format) {
        case 'csv':
          return getCSVContent(data);
        case 'json':
        default:
          return Promise.resolve(JSON.stringify(data, null, 2));
      }
    })
    .then((content) => {
      if (outputPath) {
        return utility.writeFile(content, outputPath);
      }
      console.log(content); // eslint-disable-line no-console
      return Promise.resolve();
    })
    .then(() => mongoClient.disconnect())
    .then(() => {
      console.log('Get statistics successfully'); // eslint-disable-line no-console
    })
    .catch((err) => {
      console.error('Failed to get statistics:'); // eslint-disable-line no-console
      console.error(err); // eslint-disable-line no-console
    });
}

function main() {
  if (require.main !== module) {
    return;
  }

  program
    .usage('[options]')
    .option('-f, --format <string>', 'Set output format', 'json')
    .option('-o, --output <string>', 'Set output path')
    .parse(process.argv);

  getStatistics(program.format, program.output);
}

main();
